#include "stdafx.h"
#include "CppUnitTest.h"
#include <vex/iterable/concept/range_concepts.h>
#include <vex/query/from.h>
#include <vex/query/where.h>
#include <vex/query/select.h>
#include <vex/iterable/make_any_range.h>
#include <vex.tests/mstest/tests.range/range_archetype.h>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace tests { namespace query_query_select {
    TEST_CLASS(test_class) {
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.query.select")
        END_TEST_CLASS_ATTRIBUTE();

        VEX_TESTS_TRACKED_MEM_CHECK();
    public:
        TEST_METHOD_INITIALIZE(set_up)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_START();
        }

        TEST_METHOD_CLEANUP(tear_down)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_FINISH();
        }

        TEST_METHOD(default_selectors_remove_reference_should_yield_expected_types)
        {
            {
                typedef std::vector<int> container_t;
                container_t c;
                auto s = vex::query::selectors::remove_reference();
                Assert::IsTrue(std::is_same<container_t::value_type, decltype(s(*c.begin()))>::value);
            }

            {
                typedef const std::vector<int> container_t;
                container_t c;
                auto s = vex::query::selectors::remove_reference();
                Assert::IsTrue(std::is_same<container_t::value_type, decltype(s(*c.begin()))>::value);
            }
        }

        TEST_METHOD(default_selectors_get_ith_should_yield_expected_types_for_map)
        {
            {
                typedef std::map<int, std::string> container_t;
                container_t c;
                auto sel_first = vex::query::selectors::get_ith<0>();
                Assert::IsTrue(std::is_same<
                    container_t::value_type::first_type const&, 
                    decltype(sel_first(*c.begin()))
                >::value);

                auto sel_second = vex::query::selectors::get_ith<1>();
                Assert::IsTrue(std::is_same<
                    container_t::value_type::second_type&, 
                    decltype(sel_second(*c.begin()))
                >::value);

                c[42] = "bimbo";
                container_t::value_type::first_type const& f = sel_first(*c.begin()); 
                Assert::AreEqual(&f, &(c.begin()->first));

                container_t::value_type::second_type& s = sel_second(*c.begin()); 
                Assert::IsTrue(std::addressof(s) == std::addressof(c.begin()->second));
            }

            {
                typedef const std::map<int, std::string> container_t;

                container_t::value_type t_values[] = {
                    std::make_pair(42, "bimbo")
                };

                container_t c(t_values, t_values + 1);

                auto sel_first = vex::query::selectors::get_ith<0>();
                Assert::IsTrue(std::is_same<
                    container_t::value_type::first_type const&, 
                    decltype(sel_first(*c.begin()))
                >::value);

                auto sel_second = vex::query::selectors::get_ith<1>();
                Assert::IsTrue(std::is_same<
                    container_t::value_type::second_type const&, 
                    decltype(sel_second(*c.begin()))
                >::value);

                container_t::value_type::first_type const& f = sel_first(*c.begin()); 
                Assert::AreEqual(&f, &(c.begin()->first));

                container_t::value_type::second_type const& s = sel_second(*c.begin()); 
                Assert::IsTrue(std::addressof(s) == std::addressof(c.begin()->second));
            }
        }

        TEST_METHOD(default_selectors_get_ith_should_yield_expected_types_for_std_pair)
        {
            auto sel_first = vex::query::selectors::get_ith<0>();
            auto sel_second = vex::query::selectors::get_ith<1>();
            {
                // pair r-value
                Assert::IsTrue(std::is_same<
                    std::pair<int, std::string>::first_type&&,
                    decltype(sel_first(std::make_pair(0, std::string("bimbo"))))
                >::value);

                Assert::IsTrue(std::is_same<
                    std::pair<int, std::string>::second_type&&,
                    decltype(sel_second(std::make_pair(0, std::string("bimbo"))))
                >::value);

                auto t_first = sel_first(std::make_pair(42, "bimbo"));
                Assert::AreEqual(42, t_first);

                auto t_second = sel_second(std::make_pair(0, std::string("bimbo"))); 
                Assert::AreEqual(std::string("bimbo"), t_second);
            }

            { // pair l-value
                auto t_lvalue = std::make_pair(42, std::string("bimbo"));
                
                Assert::IsTrue(std::is_same<
                    std::pair<int, std::string>::first_type&,
                    decltype(sel_first(t_lvalue))
                >::value);

                Assert::IsTrue(std::is_same<
                    std::pair<int, std::string>::second_type&,
                    decltype(sel_second(t_lvalue))
                >::value);

                auto& t_first = sel_first(t_lvalue);
                Assert::AreEqual(std::addressof(t_lvalue.first), std::addressof(t_first));

                auto& t_second = sel_second(t_lvalue); 
                Assert::IsTrue(std::addressof(t_lvalue.second) == std::addressof(t_second));
            }

            { // pair const l-value
                auto const t_clvalue = std::make_pair(42, std::string("bimbo"));

                Assert::IsTrue(std::is_same<
                    std::pair<int, std::string>::first_type const&,
                    decltype(sel_first(t_clvalue))
                >::value);

                Assert::IsTrue(std::is_same<
                    std::pair<int, std::string>::second_type const&,
                    decltype(sel_second(t_clvalue))
                >::value);

                auto& t_first = sel_first(t_clvalue);
                Assert::AreEqual(std::addressof(t_clvalue.first), std::addressof(t_first));

                auto& t_second = sel_second(t_clvalue); 
                Assert::IsTrue(std::addressof(t_clvalue.second) == std::addressof(t_second));
            }
        }

        TEST_METHOD(default_selectors_get_ith_should_yield_expected_types_for_std_tuple)
        { // std::tuple
            std::tuple<int, double, std::string> t_lvalue = 
                std::make_tuple(42, 42.1, "bimbo");

            auto& e0 = vex::query::selectors::get_ith<0>()(t_lvalue);
            Assert::IsTrue(&e0 == &std::get<0>(t_lvalue));

            auto& e1 = vex::query::selectors::get_ith<1>()(t_lvalue);
            Assert::IsTrue(&e1 == &std::get<1>(t_lvalue));

            auto& e2 = vex::query::selectors::get_ith<2>()(t_lvalue);
            Assert::IsTrue(&e2 == &std::get<2>(t_lvalue));
        }

        TEST_METHOD(default_selectors_get_ith_should_yield_expected_types_for_boost_tuple)
        { // boost::tuple
            boost::tuple<int, double, std::string> t_lvalue = 
                boost::make_tuple(42, 42.1, "bimbo");

            auto& e0 = vex::query::selectors::get_ith<0>()(t_lvalue);
            Assert::IsTrue(&e0 == &boost::get<0>(t_lvalue));

            auto& e1 = vex::query::selectors::get_ith<1>()(t_lvalue);
            Assert::IsTrue(&e1 == &boost::get<1>(t_lvalue));

            auto& e2 = vex::query::selectors::get_ith<2>()(t_lvalue);
            Assert::IsTrue(&e2 == &boost::get<2>(t_lvalue));
        }

        TEST_METHOD(select_adapter_can_be_constructed_from_range_and_selector)
        {
            {
                typedef std::vector<int> container_t;
                // removes l-value reference from result:
                auto s = vex::query::selectors::remove_reference();//[](container_t::reference v) { return v; }; 
                typedef decltype(vex::query::from(std::declval<container_t&>())) range_t;    

                typedef vex::query::adapter::detail::select_dispatch<decltype(s)>::selector_type select_t;
                typedef vex::query::adapter::select_adapter<range_t, select_t> adapter_t;

                Assert::IsTrue(std::is_same<container_t::value_type, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<range_t::category, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::RandomAccessRange<adapter_t>));
            }

            {
                typedef std::map<int, std::string> container_t;
                auto s = vex::query::selectors::get_ith<1>();
                typedef decltype(vex::query::from(std::declval<container_t&>())) range_t;

                typedef vex::query::adapter::detail::select_dispatch<decltype(s)>::selector_type select_t; 
                typedef vex::query::adapter::select_adapter<range_t, select_t> adapter_t;

                Assert::IsTrue(std::is_same<
                    container_t::value_type::second_type&, 
                    adapter_t::reference
                >::value,  wlog_message() <<
                "expected: " << typeid(container_t::value_type::second_type).name() << "&" << std::endl <<
                "got: " << 
                typeid(adapter_t::reference).name() << 
                (std::is_reference<adapter_t::reference>::value ? "&" : "") << std::endl | as_string);

                Assert::IsTrue(std::is_same<range_t::category, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::map<int, std::string> container_t;
                auto s = vex::query::selectors::get_ith<1>();
                typedef decltype(vex::query::from(std::declval<container_t const&>())) range_t;

                typedef vex::query::adapter::detail::select_dispatch<decltype(s)>::selector_type select_t; 
                typedef vex::query::adapter::select_adapter<range_t, select_t> adapter_t;

                Assert::IsTrue(std::is_same<
                    container_t::value_type::second_type const&, 
                    adapter_t::reference
                >::value);
                Assert::IsTrue(std::is_same<range_t::category, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::map<int, const std::string> container_t;
                auto s = vex::query::selectors::get_ith<1>();
                typedef decltype(vex::query::from(std::declval<container_t const&>())) range_t;

                typedef vex::query::adapter::detail::select_dispatch<decltype(s)>::selector_type select_t; 
                typedef vex::query::adapter::select_adapter<range_t, select_t> adapter_t;

                Assert::IsTrue(std::is_same<
                    container_t::value_type::second_type const&, 
                    adapter_t::reference
                >::value);
                Assert::IsTrue(std::is_same<range_t::category, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::map<int, const std::string> container_t;
                auto s = vex::query::selectors::get_ith<0>();
                typedef decltype(vex::query::from(std::declval<container_t const&>())) range_t;

                typedef vex::query::adapter::detail::select_dispatch<decltype(s)>::selector_type select_t; 
                typedef vex::query::adapter::select_adapter<range_t, select_t> adapter_t;

                Assert::IsTrue(std::is_same<
                    container_t::value_type::first_type const&, 
                    adapter_t::reference
                >::value);
                Assert::IsTrue(std::is_same<range_t::category, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }
        }

        TEST_METHOD(select_clause_from_stl_containers)
        {
            {
                typedef std::map<int, std::string> container_t;
                auto p = [](int v) { return v % 2 == 0; };
                typedef decltype(vex::query::from(std::declval<container_t&>())
                    | vex::query::select(vex::query::selectors::get_ith<0>())
                    | vex::query::where(p)) adapter_t;

                Assert::IsTrue(std::is_same<container_t::value_type::first_type&, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::map<int, std::string> container_t;
                auto p = [](int v) { return v % 2 == 0; };
                auto s = [](container_t::value_type const& v) { return v.first; };
                typedef decltype(vex::query::from(std::declval<container_t&>())
                    | vex::query::select(s)
                    | vex::query::where(p)) adapter_t;

                Assert::IsTrue(std::is_same<int, adapter_t::reference>::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }

            {
                typedef std::map<int, std::string> container_t;
                auto p = [](std::string const& v) { return v.length() > 3u; };
                typedef decltype(vex::query::from(std::declval<container_t&>())
                    | vex::query::select(vex::query::selectors::get_ith<1>())
                    | vex::query::where(p)) adapter_t;

                Assert::IsTrue(std::is_same<
                    container_t::value_type::second_type&, 
                    adapter_t::reference
                >::value);
                Assert::IsTrue(std::is_same<vex::iterable::bidirectional, adapter_t::category>::value);
                BOOST_CONCEPT_ASSERT((vex::iterable::concept::BiDirectionalRange<adapter_t>));
            }
        }

    };
}}